package com.mathdroid.model.items;

import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;

import com.mathdroid.exceptionhandling.ExceptionHandler;
import com.mathdroid.model.Stats;
import com.mathdroid.model.enums.EArmorProficiency;
import com.mathdroid.model.enums.EClass;
import com.mathdroid.model.enums.EItemSlot;
import com.mathdroid.model.enums.EItemSource;
import com.mathdroid.model.enums.EItemStat;
import com.mathdroid.model.enums.EItemType;
import com.mathdroid.model.enums.EModuleProficiency;
import com.mathdroid.model.enums.ERarity;
import com.mathdroid.model.enums.EWeaponProficiency;

/**
 * This is an item. Items can contain items.
 */
public final class Item implements Serializable, Cloneable {
	
	/**
	 * Generated UID.
	 */
	private static final long serialVersionUID = 6483576735126573906L;
	
	/**
	 * The type of the item.
	 */
	private EItemType type = null;
	
	/**
	 * The slot(s) the item fits in.
	 */
	private LinkedList<EItemSlot> slots = new LinkedList<EItemSlot>();
	
	/**
	 * The armor proficiency that is required to use this item, if this is
	 * an armor.
	 */
	private EArmorProficiency armorProficiency = null;
	
	/**
	 * The weapon proficiency that is required to use this item, 
	 * if this is a weapon.
	 */
	private EWeaponProficiency weaponProficiency = null;
	
	/**
	 * The module proficiency that is required to use this item, 
	 * if this is a module.
	 */
	private EModuleProficiency moduleProficiency = null;
	
	/**
	 * The item id on torhead item page URLs.
	 */
	private String torHeadID = null;
	
	/**
	 * This item is usable by this class(es).
	 */
	private LinkedList<EClass> usableByClass = new LinkedList<EClass>();
	
	/**
	 * The source(s) of this item, some items may have multiple sources.
	 */
	private LinkedList<EItemSource> sources = new LinkedList<EItemSource>();
	
	/**
	 * The module(s) of this item.
	 */
	private HashMap<EModuleProficiency, Item> modules = 
		new HashMap<EModuleProficiency, Item>();
	
	/**
	 * The name of the item.
	 */
	private String name = null;
	
	/**
	 * The stats of this item. This is the difference between item 
	 * stats and the sum of the default modules.
	 */
	private Stats stats = new Stats();
	
	/**
	 * Maybe the item has a use effect.
	 */
	private String useText = null;
		
	/**
	 * The icon of this item.
	 */
	private String imageName = null;
	
	/**
	 * The rating of this item.
	 */
	private double rating = -1;

	/**
	 * The item level.
	 */
	private int itemLevel = -1;
	
	/**
	 * The item rarity.
	 */
	private ERarity rarity = null;
	
	/**
	 * If this item is unique equipped.
	 */
	private boolean unique = false;
	
	/**
	 * If this item is available for the optimizer.
	 */
	private boolean available = false;
	
	/**
	 * Get the HTML text for the tag-label of item ToolTips.
	 * @return Item tags.
	 */
	public String getTagsText() {
		String s = new String("<html>");
		
		if (this.armorProficiency != null) {
			s += "[" + this.armorProficiency.toString() + "] ";
		}
		
		if (this.weaponProficiency != null) {
			s += "[" + this.weaponProficiency.toString() + "] ";
		}
		
		if (this.itemLevel != 0) {
			s += "[ilvl " + this.itemLevel + "] ";
		}
		
		if (this.rarity != null) {
			s +=  "[" + this.rarity.toString() + "] ";
		}
		
		
		if (this.unique) {
			s += "[unique] ";
		}
		
		if (this.usableByClass.size() > 0) {
			s += "<br>";
		}
		
		for (EClass c : this.usableByClass) {
			s += " [" + c + "]";
		}
		
		s += "<br>";
		for (EItemSource src : this.sources) {
			s += " [" + src + "]";
		}
		
		return s + "</html>";
	}
	
	/* *************************** GETTERS / SETTERS ************************ */

	/**
	 * @return the usableByClass
	 */
	public LinkedList<EClass> getUsableByClass() {
		return usableByClass;
	}

	/**
	 * @param u the usableByClass to set
	 */
	public void setUsableByClass(final LinkedList<EClass> u) {
		this.usableByClass = u;
	}

	/**
	 * @return the sources
	 */
	public LinkedList<EItemSource> getSources() {
		return sources;
	}

	/**
	 * @param s the sources to set
	 */
	public void setSources(final LinkedList<EItemSource> s) {
		this.sources = s;
	}

	/**
	 * @return the modules
	 */
	public HashMap<EModuleProficiency, Item> getModules() {
		return modules;
	}
	
	/**
	 * Add a default module. This adjusts the default stats of this item.
	 * After adding all default modules, the default stats should never
	 * adjusted again.
	 * @param i Module item.
	 */
	public void addDefaultModule(final Item i) {
		EModuleProficiency p = i.getModuleProficiency();
		int newValue = 0;
		
		this.modules.put(p, i);

		if (this.stats != null) {
			for (EItemStat s : i.getStats().keySet()) {	
				if (i.getStats() == null) {
					System.err.println("FATAL: THE MODULE " + i.getName() 
							+ " IN ITEM " + this.name 
							+ " HAS NO STATS.");
					i.setStats(new Stats());
				}
				if (this.stats.get(s) == null) {
					System.err.println("FATAL: THE ITEM " + this.name 
							+ " SHOULD HAVE THE STAT " + s.toString());
				}
				newValue = 
					this.stats.get(s) - i.getStats().get(s);
				this.stats.put(s, newValue);
				
				if (newValue < 0) {
					System.err.println("FATAL: THE ITEM " + this.name 
							+ " HAS NEGATIVE DEFAULT STATS.");
				}
			}
		} else {
			System.err.println("WARNING: THE ITEM " + this.name 
					+ " HAS NO STATS.");
			this.stats = new Stats();
		}
	}

	/**
	 * @param m the modules to set
	 */
	public void setModules(final HashMap<EModuleProficiency, Item> m) {
		this.modules = m;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/**
	 * @param n the name to set
	 */
	public void setName(final String n) {
		this.name = n;
	}

	/**
	 * @return the stats
	 */
	public Stats getStats() {
		return stats;
	}

	/**
	 * @param s the stats to set
	 */
	public void setStats(final Stats s) {
		this.stats = s;
	}

	/**
	 * @return the procText
	 */
	public String getUseText() {
		return useText;
	}

	/**
	 * @param s the use text to set
	 */
	public void setUseText(final String s) {
		this.useText = s;
	}

	/**
	 * @return the imageName
	 */
	public String getImageName() {
		return imageName;
	}

	/**
	 * @param n the imageName to set
	 */
	public void setImageName(final String n) {
		this.imageName = n;
	}

	/**
	 * @return the rating
	 */
	public double getRating() {
		return rating;
	}

	/**
	 * @param r the rating to set
	 */
	public void setRating(final double r) {
		this.rating = r;
	}

	/**
	 * @return the itemLevel
	 */
	public int getItemLevel() {
		return itemLevel;
	}

	/**
	 * @param ilvl the itemLevel to set
	 */
	public void setItemLevel(final int ilvl) {
		this.itemLevel = ilvl;
	}

	/**
	 * @return the rarity
	 */
	public ERarity getRarity() {
		return rarity;
	}

	/**
	 * @param r the rarity to set
	 */
	public void setRarity(final ERarity r) {
		this.rarity = r;
	}

	/**
	 * @return the unique
	 */
	public boolean isUnique() {
		return unique;
	}

	/**
	 * @param u the unique to set
	 */
	public void setUnique(final boolean u) {
		this.unique = u;
	}

	/**
	 * @param a If the item is available for the optimizer.
	 */
	public void setAvailable(final boolean a) {
		this.available = a;
	}

	/**
	 * @return If the item is available for the optimizer.
	 */
	public boolean isAvailable() {
		return available;
	}

	/**
	 * @return the slots
	 */
	public LinkedList<EItemSlot> getSlots() {
		return slots;
	}

	/**
	 * @param a the armorProficiency to set
	 */
	public void setArmorProficiency(final EArmorProficiency a) {
		this.armorProficiency = a;
	}

	/**
	 * @return the armorProficiency
	 */
	public EArmorProficiency getArmorProficiency() {
		return armorProficiency;
	}

	/**
	 * @param w the weaponProficiency to set
	 */
	public void setWeaponProficiency(final EWeaponProficiency w) {
		this.weaponProficiency = w;
	}

	/**
	 * @return the weaponProficiency
	 */
	public EWeaponProficiency getWeaponProficiency() {
		return weaponProficiency;
	}

	/**
	 * @param t the type to set
	 */
	public void setType(final EItemType t) {
		this.type = t;
	}

	/**
	 * @return the type
	 */
	public EItemType getType() {
		return type;
	}

	/**
	 * @param m the moduleProficiency to set
	 */
	public void setModuleProficiency(final EModuleProficiency m) {
		this.moduleProficiency = m;
	}

	/**
	 * @return the moduleProficiency
	 */
	public EModuleProficiency getModuleProficiency() {
		return moduleProficiency;
	}

	/**
	 * Add an item slot.
	 * @param s Item slot.
	 */
	public void addSlot(final EItemSlot s) {
		this.slots.add(s);
	}
	
	/**
	 * Add an item source.
	 * @param s Item source.
	 */
	public void addSource(final EItemSource s) {
		this.sources.add(s);
	}

	/**
	 * Build HTML text for labels.
	 * @return HTML text.
	 */
	public String getTotalStatsTextHTML() {
		String text = new String("<html>Total Stats:<br>");
		int total = 0;
		
		for (EItemStat estat : EItemStat.getAllStats()) {
			total = this.getTotalStat(estat);
			if (total > 0) {
				text += total + " " + estat.getName() + "<br>";
			}
		}
		
		return text + "</html>";
	}
	
	/**
	 * Get the HTML text for the "use effect", if there is any.
	 * @return "Use effect" HTML text.
	 */
	public String getUseEffectTextHTML() {
		String text = null;
		
		if (this.useText != null) {
			text = new String("<html>" + this.useText + "</html>");
		}
		
		return text;
	}
	
	/**
	 * Get the total stats of a type. This is the sum of default item stats
	 * and all module stats of the type.
	 * @param s Stat type.
	 * @return Total stats.
	 */
	public int getTotalStat(final EItemStat s) {
		int sum = 0;
		
		/* Add the default stat, if there is any */
		if (this.stats.containsKey(s)) {
			sum = this.stats.get(s);
		}
		
		/* Add the modules to this item's stats. */
		for (Item i : this.modules.values()) {
			if (i != null 
					&& i.getStats() != null 
					&& i.getStats().containsKey(s)) {
				
				sum += i.getStats().get(s);
			}
		}
		
		return sum;
	}

	/**
	 * @param id the torHeadID to set
	 */
	public void setTorHeadID(final String id) {
		this.torHeadID = id;
	}

	/**
	 * @return the torHeadID
	 */
	public String getTorHeadID() {
		return torHeadID;
	}
	
	@Override
	public Object clone() {
		Object o = null;
		
		try {
			o = super.clone();
		} catch (CloneNotSupportedException e) {
			ExceptionHandler.report("Could not clone item.", e);
		}
		
		return o;
	}
}
